/*
 * Copyright (C) 2006 Johan Maasing johan at zoom.nu Licensed under the Apache
 * License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
 * or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package nu.zoom.catonine.stylerule;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Manages a collection of StyleRules.
 * 
 * @author "Johan Maasing" &lt;johan@zoom.nu&gt;
 */
@XmlRootElement(name = "StyleRulesCollection")
public class StyleRulesManager implements Serializable, ChangeTracker {
	private static final long serialVersionUID = 1L;
	private transient Log log = LogFactory.getLog(getClass());
	private transient boolean changed = false;
	private final ArrayList<StyleRulesManagerListener> listeners = new ArrayList<StyleRulesManagerListener>();
	@XmlElement
	private ArrayList<StyleRules> styleRules;

	public StyleRulesManager() {
		super();
		styleRules = new ArrayList<StyleRules>();
	}

	/**
	 * Add a new list of style rules to the manager.
	 * 
	 * @param styleRulesToAdd
	 *            The style rules to add, may not be null.
	 */
	public synchronized void add(StyleRules styleRulesToAdd) {
		if (styleRulesToAdd == null) {
			throw new NullPointerException("newRuleSet may not be null");
		}
		log.trace("Adding styleRules: " + styleRulesToAdd);
		styleRules.add(styleRulesToAdd);
		log.trace("Sorting style rules");
		Collections.sort(styleRules, new StyleRulesComparator());
		fireStructureChanged();
	}

	/**
	 * Remove a style rules from the manager.
	 * 
	 * @param styleRulesToRemove
	 *            The style rules to remove.
	 */
	public synchronized void remove(StyleRules styleRulesToRemove) {
		log.trace("Removing style rules: " + styleRulesToRemove);
		styleRules.remove(styleRulesToRemove);
		log.trace("Sorting style rules");
		Collections.sort(styleRules, new StyleRulesComparator());
		fireStructureChanged();
	}

	public synchronized List<StyleRules> getStyleRules() {
		return Collections.unmodifiableList(styleRules);
	}

	/**
	 * Add a listener for changes to the set of style rules managed by this
	 * instance.
	 * 
	 * @param listener
	 *            The listener to add, may not be null.
	 */
	public void addListener(StyleRulesManagerListener listener) {
		if (listener == null) {
			throw new NullPointerException("Listener may not be null");
		}
		synchronized (listeners) {
			listeners.add(listener);
		}
	}

	/**
	 * Remove a listener.
	 * 
	 * @param listener
	 */
	public void removeListener(StyleRulesManagerListener listener) {
		synchronized (listeners) {
			listeners.remove(listener);
		}
	}

	public void fireStructureChanged() {
		changed = true;
		ArrayList<Thread> notifierThreads = new ArrayList<Thread>();
		synchronized (listeners) {
			log.debug("Notifying: " + listeners.size() + " listeners that structure has changed");
			for (final StyleRulesManagerListener listener : listeners) {
				notifierThreads.add(new Thread(new Runnable() {

					@Override
					public void run() {
						listener.styleRulesChanged(StyleRulesManager.this);
					}
				}));
			}
		}
		for (Thread t : notifierThreads) {
			t.start();
		}
	}

	public void copyListenersTo(StyleRulesManager newManager) {
		newManager.listeners.addAll(this.listeners);
	}

	public void importManager(StyleRulesManager importFrom) {
		for (StyleRules importRules : importFrom.styleRules) {
			boolean shouldImport = true;
			for (StyleRules myRules : this.styleRules) {
				if (myRules.getGuid().equals(importRules.getGuid())) {
					shouldImport = false;
					break;
				}
			}
			if (shouldImport) {
				add(importRules);
			}
		}
	}

	public class StyleRulesComparator implements Comparator<StyleRules> {
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.Comparator#compare(T, T)
		 */

		@Override
		public int compare(StyleRules rules1, StyleRules rules2) {
			if (((StyleRules) rules1).getName() != null) {
				return ((StyleRules) rules1).getName().compareTo(((StyleRules) rules2).getName());
			} else {
				return 1;
			}
		}
	}

	@Override
	public synchronized boolean isChanged() {
		if (!changed) {
			for (StyleRules rules : styleRules) {
				if (rules.isChanged()) {
					log.trace("Found a style rules that is changed, setting changed status");
					changed = true;
				}
			}
		}
		return changed;
	}

	@Override
	public synchronized void resetChangeStatus() {
		changed = false;
		log.trace("Resetting changed status");
		for (StyleRules rules : styleRules) {
			rules.resetChangeStatus();
		}
	}
}
